package warenautomat;

import warenautomat.SystemSoftware;

/**
 * Die Kasse verwaltet das eingenommene Geld sowie das Wechselgeld. <br>
 * Die Kasse hat fünf Münz-Säulen für: <br>
 * - 10 Rappen <br>
 * - 20 Rappen <br>
 * - 50 Rappen <br>
 * - 1 Franken <br>
 * - 2 Franken <br>
 */

public class Kasse {
   private Muenzsaeule mMuenzsaeule[];
   private int mAktuellerBetrag;
   private int mBetragVerkaufteWaren;

   private int mGewaehlteMuenzsaeule;
   private int mGewaehlteAnzahl;
   
   private static final int MUENZEN[] = { 10, 20, 50, 100, 200 }; // Münzenwert
                                                                  // in Rappen
                                                                  // dem Wert
                                                                  // aufsteigend
                                                                  // geordnet
   private static final int MUENZE_UNBEKANNT = -200;
   private static final int MUENZESAEULE_UNBEKANNT = -1;

   /**
    * Standard-Konstruktor. <br>
    * Führt die nütigen Initialisierungen durch.
    */
   public Kasse() {
      mMuenzsaeule = new Muenzsaeule[MUENZEN.length];
      for (int i = 0; i < MUENZEN.length; i++) {
         mMuenzsaeule[i] = new Muenzsaeule(MUENZEN[i]);
      }

      mGewaehlteMuenzsaeule = MUENZESAEULE_UNBEKANNT;
      System.out.println("Kasse::Kasse()");
   }

   /**
    * Diese Methode wird aufgerufen nachdem das Personal beim Geldauffüllen die
    * Münzart und die Anzahl der Münzen über die Tastatur eingegeben hat (siehe
    * Use-Case "Kasse auffüllen").
    * 
    * @param pMuenzenBetrag
    *           Betrag der Münzart in Franken.
    * @param pAnzahl
    *           Anzahl der neu eingelegten Münzen.
    * @return Wenn es genügend Platz in der Münzsüule hat: die Anzahl Münzen
    *         welche eingelegt werden (d.h. pAnzahl). <br>
    *         Wenn es nicht genügend Platz hat: die Anzahl Münzen welche nicht
    *         Platz haben werden, als negative Zahl (z.B. -20). <br>
    *         Wenn ein nicht unterstützter Münzbetrag übergeben wurde: -200
    */
   public int fuelleKasse(double pMuenzenBetrag, int pAnzahl) {
      mGewaehlteAnzahl = MUENZE_UNBEKANNT;
      mGewaehlteMuenzsaeule = MUENZESAEULE_UNBEKANNT;

      // Suche korrekte Münzesäule
      int muenzeInRappen = convFrankenToRappen(pMuenzenBetrag);
      for (int i = 0; i < MUENZEN.length; i++) {
         if (muenzeInRappen == mMuenzsaeule[i].getMuenzwertInRappen()) {
            // Münzesüule gefunden
            mGewaehlteMuenzsaeule = i;
            int maxInhalt = mMuenzsaeule[mGewaehlteMuenzsaeule].getMaxAnzahl();

            mGewaehlteAnzahl = mMuenzsaeule[mGewaehlteMuenzsaeule].getAktuelleAnzahl() + pAnzahl;
            if (mGewaehlteAnzahl > maxInhalt) {
               // Fehler: zu viele Münzen --> Zeige, wieviele Münzen zu viel
               // sind als negative Zahl an
               mGewaehlteAnzahl = maxInhalt - mGewaehlteAnzahl;
            } else if (mGewaehlteAnzahl < 0) {
               // Fehler: nicht genügend Münzen vorhanden --> setzte Wert auf 0
               mGewaehlteAnzahl = 0;
            } else {
               // kein Fehler
               mGewaehlteAnzahl = pAnzahl;
            }
            break;
         }
      }
      System.out.println("Kasse::fuelleKasse(" + pMuenzenBetrag + "Fr., "+ pAnzahl + "): "
            + (mGewaehlteAnzahl == MUENZE_UNBEKANNT || mGewaehlteAnzahl != pAnzahl ? "fehlerhaft" : "erfolgreich"));
      return mGewaehlteAnzahl;
   }

   /**
    * Diese Methode wird aufgerufen nachdem das Personal beim Geldauffüllen den
    * Knopf "Bestütigen" gedrückt hat. (siehe Use-Case "Kasse auffüllen"). <br>
    * Verbucht die Münzen gemüss dem vorangegangenen Aufruf der Methode
    * <code> fuelleKasse() </code>.
    */
   public void fuelleKasseBestaetigung() {
      if (mGewaehlteAnzahl != MUENZE_UNBEKANNT && mGewaehlteMuenzsaeule != MUENZESAEULE_UNBEKANNT) {
         if (mGewaehlteAnzahl > 0) {
            // Münzen einfügen
            for (int i = 0; i < mGewaehlteAnzahl; i++) {
               mMuenzsaeule[mGewaehlteMuenzsaeule].einfuegenMuenze();
            }
         } else {
            // Münzen entnehmen
            for (int i = 0; i > mGewaehlteAnzahl; i--) {
               mMuenzsaeule[mGewaehlteMuenzsaeule].entnehmeMuenze();
            }
         }
         SystemSoftware.zeigeMuenzenInGui(mMuenzsaeule[mGewaehlteMuenzsaeule].getMuenzwertInRappen() / 100.0,
               mMuenzsaeule[mGewaehlteMuenzsaeule].getAktuelleAnzahl());
      }

      mGewaehlteAnzahl = MUENZE_UNBEKANNT;
      mGewaehlteMuenzsaeule = MUENZESAEULE_UNBEKANNT;
      System.out.println("Kasse::fuelleKasseBestaetigung(): ");
   }

   /**
    * Diese Methode wird aufgerufen wenn ein Kunde eine Münze eingeworfen hat. <br>
    * Führt den eingenommenen Betrag entsprechend nach. <br>
    * Stellt den nach dem Einwerfen vorhandenen Betrag im Kassen-Display dar. <br>
    * Eingenommenes Geld steht sofort als Wechselgeld zur Verfügung. <br>
    * Die Münzen werden von der Hardware-Kasse auf Falschgeld, Fremdwührung und
    * nicht unterstützte Münzarten geprüft, d.h. diese Methode wird nur
    * aufgerufen wenn ein Münzeinwurf soweit erfolgreich war. <br>
    * Ist die Münzsüule voll (d.h. 100 Münzen waren vor dem Einwurf bereits
    * darin enthalten), so wird mittels
    * <code> SystemSoftware.auswerfenWechselGeld() </code> unmittelbar ein
    * entsprechender Münz-Auswurf ausgeführt. <br>
    * Hinweis: eine Hardware-Münzsüule hat jeweils effektiv Platz für 101
    * Münzen.
    * 
    * @param pMuenzenBetrag
    *           Der Betrag der neu eingeworfenen Münze in Franken.
    * @return <code> true </code>, wenn er Einwurf erfolgreich war. <br>
    *         <code> false </code>, wenn Münzsüule bereits voll war.
    */
   public boolean einnehmen(double pMuenzenBetrag) {
      int muenzeEingenommen = fuelleKasse(pMuenzenBetrag, 1);
      boolean einnehmenOk = muenzeEingenommen > 0;
      
      System.out.println("Kasse::einnehmen("+ pMuenzenBetrag +"Fr.): erfolgreich");

      if (einnehmenOk) {
         fuelleKasseBestaetigung();
         mAktuellerBetrag += convFrankenToRappen(pMuenzenBetrag);
         System.out.println("Kasse::einnehmen("+ pMuenzenBetrag +"Fr.): erfolgreich");
         SystemSoftware.zeigeBetragAn(mAktuellerBetrag / 100.0);
      } else {
         // Fehler --> Münzen auswerfen
         SystemSoftware.auswerfenWechselGeld(pMuenzenBetrag);
         if (muenzeEingenommen == MUENZE_UNBEKANNT) {
            System.out.println("Kasse::einnehmen("+ pMuenzenBetrag +"Fr.): fehlerhaft. Münze unbekannt");
         } else {
            System.out.println("Kasse::einnehmen("+ pMuenzenBetrag +"Fr.): fehlerhaft. Münzsüule voll");
         }
      }

      return einnehmenOk;
   }

   /**
    * Bewirkt den Auswurf des Restbetrages.
    */
   public void gibWechselGeld() {
      boolean rueckgabeOk = true;
      for (int saeule = MUENZEN.length - 1; saeule >= 0 && mAktuellerBetrag > 0; saeule--) {
         int muenze = mMuenzsaeule[saeule].getMuenzwertInRappen();
         while (rueckgabeOk && (mMuenzsaeule[saeule].getAktuelleAnzahl() > 0) && (mAktuellerBetrag / muenze > 0)) {
            rueckgabeOk = mMuenzsaeule[saeule].entnehmeMuenze();
            if (rueckgabeOk) {
               mAktuellerBetrag -= muenze;
               SystemSoftware.auswerfenWechselGeld(muenze / 100.0);
               SystemSoftware.zeigeBetragAn(mAktuellerBetrag / 100.0);
               SystemSoftware.zeigeMuenzenInGui(muenze / 100.0, mMuenzsaeule[saeule].getAktuelleAnzahl());
            }
         }
      }
      System.out.println("Kasse::gibWechselgeld(): ");
   }

   /*
    * überprüft, ob genug Geld eingeworfen wurde um eine Ware zu kaufen
    * 
    * @pBetragInRappen eingeworfener Geldbetrag
    */
   public boolean istGenugGeldVorhanden(int pBetragInRappen){
      boolean genugGeldVorhanden = false;
      
      if(mAktuellerBetrag - pBetragInRappen >= 0){
         genugGeldVorhanden = true;
      }
      
      if(genugGeldVorhanden == false){
         SystemSoftware.zeigeZuWenigGeldAn();
      }
      
      System.out.println("Kasse::istGenugGeldVorhanden("+pBetragInRappen+"Rp.): " + (genugGeldVorhanden ? "ja" : "nein"));
      return genugGeldVorhanden;
   }
   
   /*
    * überprüft, ob auf den Betrag Geld zurückgegeben werden kann.
    * Zurückgegebener Geldbetrag würe = Gesamtgeldbetrag - pBetragInRappen
    * 
    * @pBetragInRappen Geld-Betrag
    */
   public boolean istWechselgeldVorhanden(int pBetragInRappen) {
      boolean wechselgeldVorhanden = false;

      int differenzBetrag = mAktuellerBetrag - pBetragInRappen;

      for (int saeule = MUENZEN.length - 1; saeule >= 0 && differenzBetrag > 0; saeule--) {
         int muenze = mMuenzsaeule[saeule].getMuenzwertInRappen();
         int zaehler = 0;
         int muenzStand = mMuenzsaeule[saeule].getAktuelleAnzahl();
         while ((muenzStand > zaehler) && (differenzBetrag / muenze > 0)) {
            differenzBetrag -= muenze;
            zaehler++;
         }
      }

      if (differenzBetrag == 0) {
         wechselgeldVorhanden = true;
      } else{
         SystemSoftware.zeigeZuWenigWechselGeldAn();
      }

      System.out.println("Kasse::istWechselgeldVorhanden("+pBetragInRappen+"Rp.): " + (wechselgeldVorhanden ? "ja" : "nein"));
      return wechselgeldVorhanden;
   }

   /*
    * Wird ein Produkt verkauft, wird diese Methode aufgerufen, um den
    * notwendigen Geldbetrag abzubuchen
    * 
    * @pBetragInRappen Geld-Betrag
    */
   public void abbuchenBetrag(int pBetragInRappen) {
      mBetragVerkaufteWaren += pBetragInRappen;
      mAktuellerBetrag -= pBetragInRappen;
      System.out.println("Kasse::abbuchenBetrag("+pBetragInRappen+"Rp.): ");
      SystemSoftware.zeigeBetragAn(mAktuellerBetrag / 100.0);
      
   }

   /**
    * Gibt den Gesamtbetrag der bisher verkauften Waren zurück. <br>
    * Analyse: Abgeleitetes Attribut.
    * 
    * @return Gesamtbetrag der bisher verkauften Waren in Franken.
    */
   public double gibBetragVerkaufteWaren() {
      return mBetragVerkaufteWaren / 100.0;
   }

   public int convFrankenToRappen(double pFranken) {
      return (int) Math.round(pFranken * 100);
   }
}
